#!/usr/bin/env python3
"""
Hyperliquid Grid Trading Strategy based on Hummingbot Framework

This strategy implements a grid trading algorithm for both spot and perpetual markets
on Hyperliquid using Hummingbot's strategy framework.
"""

import logging
from decimal import Decimal
from typing import Dict, List, Optional

from framework.strategy_base import StrategyBase, OrderType, TradeType, PositionSide, OrderFilledEvent, OrderFailedEvent, LimitOrder
import asyncio

from config.settings import Config


class HyperliquidGridStrategy(StrategyBase):
    """
    Grid Trading Strategy for Hyperliquid
    
    This strategy places buy and sell orders in a grid pattern around the current price,
    automatically rebalancing when orders are filled.
    """
    
    def __init__(self, connectors: Dict):
        """
        Initialize the grid strategy
        
        Args:
            connectors: Dictionary of exchange connectors
        """
        super().__init__(connectors)
        
        # Load configuration
        self._config = Config()
        
        # Strategy parameters
        self.trading_pair = self._config.TRADING_PAIR or "ETH-USD"
        self.grid_levels = self._config.GRID_LEVELS or 5
        self.grid_spread = Decimal(str(self._config.GRID_SPREAD or 0.01))  # 1% spread
        self.order_amount = Decimal(str(self._config.ORDER_AMOUNT or 0.1))
        self.center_price_refresh_time = self._config.CENTER_PRICE_REFRESH_TIME or 300  # 5 minutes
        
        # Grid state
        self._grid_orders: Dict[str, LimitOrder] = {}
        self._center_price: Optional[Decimal] = None
        self._last_price_update = 0
        
        # Logger
        self._logger = logging.getLogger(__name__)
        
        # Determine connector type
        self._connector_name = self._get_connector_name()
        
        self._logger.info(f"Initialized Hyperliquid Grid Strategy for {self.trading_pair}")
        self._logger.info(f"Grid levels: {self.grid_levels}, Spread: {self.grid_spread}")
    
    def _get_connector_name(self) -> str:
        """Determine which connector to use based on configuration"""
        if self._config.TRADING_MODE == "spot":
            return "hyperliquid_spot"
        else:
            return "hyperliquid_perpetual"
    
    @property
    def connector(self):
        """Get the active connector"""
        return self.connectors[self._connector_name]
    
    def on_tick(self):
        """
        Main strategy logic called on each tick
        """
        try:
            # Check if connector is ready
            if not self.connector.ready:
                self._logger.debug("Connector not ready, skipping tick")
                return
            
            # Update center price if needed
            current_time = self.current_timestamp
            if (current_time - self._last_price_update) > self.center_price_refresh_time:
                self._update_center_price()
                self._last_price_update = current_time
            
            # Check and maintain grid
            self._maintain_grid()
            
        except Exception as e:
            self._logger.error(f"Error in grid strategy tick: {e}")
    
    def _update_center_price(self):
        """Update the center price for grid calculation"""
        try:
            order_book = self.connector.get_order_book(self.trading_pair)
            if order_book:
                mid_price = (order_book.get_price(True) + order_book.get_price(False)) / 2
                self._center_price = mid_price
                self._logger.info(f"Updated center price to {self._center_price}")
            else:
                self._logger.warning(f"No order book available for {self.trading_pair}")
        except Exception as e:
            self._logger.error(f"Error updating center price: {e}")
    
    def _maintain_grid(self):
        """Maintain the grid of orders"""
        if not self._center_price:
            self._logger.debug("No center price available, cannot maintain grid")
            return
        
        # Cancel existing orders that are too far from center price
        self._cancel_outdated_orders()
        
        # Place missing grid orders
        self._place_grid_orders()
    
    def _cancel_outdated_orders(self):
        """Cancel orders that are too far from the current center price"""
        max_distance = self.grid_spread * (self.grid_levels + 1)
        
        orders_to_cancel = []
        for order_id, order in self._grid_orders.items():
            price_distance = abs(order.price - self._center_price) / self._center_price
            if price_distance > max_distance:
                orders_to_cancel.append(order_id)
        
        for order_id in orders_to_cancel:
            self._cancel_order(order_id)
    
    def _place_grid_orders(self):
        """Place grid orders around the center price"""
        # Calculate grid prices
        buy_prices = []
        sell_prices = []
        
        for i in range(1, self.grid_levels + 1):
            # Buy orders below center price
            buy_price = self._center_price * (1 - self.grid_spread * i)
            buy_prices.append(buy_price)
            
            # Sell orders above center price
            sell_price = self._center_price * (1 + self.grid_spread * i)
            sell_prices.append(sell_price)
        
        # Place buy orders
        for price in buy_prices:
            if not self._has_order_at_price(price, TradeType.BUY):
                self._place_grid_order(TradeType.BUY, price)
        
        # Place sell orders
        for price in sell_prices:
            if not self._has_order_at_price(price, TradeType.SELL):
                self._place_grid_order(TradeType.SELL, price)
    
    def _has_order_at_price(self, price: Decimal, trade_type: TradeType) -> bool:
        """Check if there's already an order at the given price and side"""
        price_tolerance = self._center_price * Decimal("0.001")  # 0.1% tolerance
        
        for order in self._grid_orders.values():
            if (order.trade_type == trade_type and 
                abs(order.price - price) < price_tolerance):
                return True
        return False
    
    def _place_grid_order(self, trade_type: TradeType, price: Decimal):
        """Place a single grid order"""
        try:
            # Quantize price and amount
            quantized_price = self.connector.quantize_order_price(self.trading_pair, price)
            quantized_amount = self.connector.quantize_order_amount(self.trading_pair, self.order_amount)
            
            # Generate order ID
            order_id = f"grid_{trade_type.name.lower()}_{int(quantized_price * 1000)}"
            
            # Place order
            self.buy_with_specific_market(
                connector_name=self._connector_name,
                trading_pair=self.trading_pair,
                amount=quantized_amount,
                order_type=OrderType.LIMIT,
                price=quantized_price,
                time_in_force="GTC"
            ) if trade_type == TradeType.BUY else self.sell_with_specific_market(
                connector_name=self._connector_name,
                trading_pair=self.trading_pair,
                amount=quantized_amount,
                order_type=OrderType.LIMIT,
                price=quantized_price,
                time_in_force="GTC"
            )
            
            # Track the order
            order = LimitOrder(
                client_order_id=order_id,
                trading_pair=self.trading_pair,
                is_buy=trade_type == TradeType.BUY,
                base_currency=self.trading_pair.split("-")[0],
                quote_currency=self.trading_pair.split("-")[1],
                amount=quantized_amount,
                price=quantized_price
            )
            
            self._grid_orders[order_id] = order
            
            self._logger.info(f"Placed grid {trade_type.name} order: {quantized_amount} {self.trading_pair} at {quantized_price}")
            
        except Exception as e:
            self._logger.error(f"Error placing grid order: {e}")
    
    def _cancel_order(self, order_id: str):
        """Cancel a specific order"""
        try:
            if order_id in self._grid_orders:
                self.cancel_order(self._connector_name, order_id)
                del self._grid_orders[order_id]
                self._logger.info(f"Cancelled grid order {order_id}")
        except Exception as e:
            self._logger.error(f"Error cancelling order {order_id}: {e}")
    
    def did_fill_order(self, event: OrderFilledEvent):
        """Handle order fill events"""
        try:
            order_id = event.order_id
            
            if order_id in self._grid_orders:
                filled_order = self._grid_orders[order_id]
                
                self._logger.info(
                    f"Grid order filled: {filled_order.trade_type.name} "
                    f"{event.amount} {self.trading_pair} at {event.price}"
                )
                
                # Remove filled order from tracking
                del self._grid_orders[order_id]
                
                # Trigger grid rebalancing
                asyncio.create_task(self._rebalance_grid())
                
        except Exception as e:
            self._logger.error(f"Error handling order fill: {e}")
    
    async def _rebalance_grid(self):
        """Rebalance the grid after an order fill"""
        try:
            # Wait a bit for market to settle
            await asyncio.sleep(1)
            
            # Update center price
            self._update_center_price()
            
            # Maintain grid
            self._maintain_grid()
            
            self._logger.info("Grid rebalanced after order fill")
            
        except Exception as e:
            self._logger.error(f"Error rebalancing grid: {e}")
    
    def did_cancel_order(self, event):
        """Handle order cancellation events"""
        order_id = event.order_id
        if order_id in self._grid_orders:
            del self._grid_orders[order_id]
            self._logger.info(f"Grid order cancelled: {order_id}")
    
    def did_fail_order(self, event):
        """Handle order failure events"""
        order_id = event.order_id
        if order_id in self._grid_orders:
            del self._grid_orders[order_id]
            self._logger.warning(f"Grid order failed: {order_id}")
    
    def format_status(self) -> str:
        """Format strategy status for display"""
        lines = []
        lines.append(f"Strategy: Hyperliquid Grid Trading")
        lines.append(f"Trading Pair: {self.trading_pair}")
        lines.append(f"Connector: {self._connector_name}")
        lines.append(f"Center Price: {self._center_price}")
        lines.append(f"Active Grid Orders: {len(self._grid_orders)}")
        
        if self._grid_orders:
            lines.append("\nGrid Orders:")
            for order_id, order in self._grid_orders.items():
                side = "BUY" if order.is_buy else "SELL"
                lines.append(f"  {side} {order.amount} at {order.price}")
        
        return "\n".join(lines)